<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>3D窗帘可视化</title>
  <style>
    * {
      padding: 0;
      margin: 0;
    }

    body {
      margin: 0;
      overflow: hidden;
      font-family: "Arial", sans-serif;
      color: #333;
      display: flex;
      flex-direction: column;
      align-items: center;
      box-sizing: border-box;
      background: #ffffff;
    }

    .container {
      display: flex;
      flex-direction: column;
      align-items: center;
      width: 100%;
      max-width: 600px;
      justify-content: center;
    }

    #scene-container {
      width: 100%;
      max-width: 600px;
      height: 400px;
      border: 2px solid rgba(0, 0, 0, 0.1);
      border-radius: 8px;
      box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
    }

    @media (max-width: 600px) {
      #scene-container {
        height: 350px;
      }

      .container {
        justify-content: flex-start;
        padding-top: 20px;
      }
    }
  </style>

  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/RGBELoader.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/OBJLoader.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/MTLLoader.js"></script>

  <!-- 导入base64变量 -->
  <script src="./js/url.js"></script>
</head>

<body>
  <div class="container">
    <div id="scene-container"></div>
  </div>

  <script>
    const haUrl = "ws://172.20.80.1:8999/api/websocket";
    const accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNjA1OTFlZmQ5ZTg0OTQ2OWMxMTM0ZDJkYjEwYjJiNyIsImlhdCI6MTc1ODE4OTQyNywiZXhwIjoyMDczNTQ5NDI3fQ.27uaWBHzibkpFBfyv37NN1puqwHtRu5bPTuY8z1a0fI";
    const entityId = "cover.living_room_curtain";
    let model;

    let scene, camera, renderer, controls;
    let curtainAssembly; // 窗帘组合组

    // 窗帘参数
    const curtainParams = {
      windowWidth: 2.0,    // 窗户宽度
      windowHeight: 1.2,   // 窗户高度
      windowY: 0.8,       // 窗户Y坐标
      segmentCount: 12,    // 窗帘片数量
      rodRadius: 0.02,     // 窗帘杆半径
      ringRadius: 0.03,    // 窗帘环半径
      ringThickness: 0.01  // 窗帘环厚度
    };

    let socket = null;
    let messageId = 1;
    let subscriptionId = null;

    function initThreeJS() {
      // 创建场景
      scene = new THREE.Scene();
      scene.background = new THREE.Color(0xffffff);

      const container = document.getElementById("scene-container");
      const aspect = container.clientWidth / container.clientHeight;

      // 创建摄像机
      camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
      camera.position.set(0, 2, 5);

      // 创建渲染器
      renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setSize(container.clientWidth, container.clientHeight);
      renderer.setPixelRatio(window.devicePixelRatio);
      container.appendChild(renderer.domElement);

      // 控制器设置
      controls = new THREE.OrbitControls(camera, renderer.domElement);
      controls.enableDamping = true;
      controls.dampingFactor = 0.05;
      controls.minDistance = 1;
      controls.maxDistance = 15;
      controls.target.set(0, 2, 0);
      controls.update();

      // 光照设置
      const ambientLight = new THREE.AmbientLight(0x808080, 0.9);
      scene.add(ambientLight);

      const frontLight = new THREE.DirectionalLight(0xffffff, 0.8);
      frontLight.position.set(3, 6, 4);
      frontLight.castShadow = true;
      scene.add(frontLight);

      const backLight = new THREE.DirectionalLight(0xffffff, 0.8);
      backLight.position.set(-3, 6, -4);
      backLight.castShadow = true;
      scene.add(backLight);

      // 初始化窗帘组合
      curtainAssembly = new THREE.Group();
      scene.add(curtainAssembly);

      // 创建窗帘
      createCurtain();

      window.addEventListener("resize", onWindowResize);

      // 加载模型（原有代码保持不变）
      const textureLoader = new THREE.TextureLoader();
      const base64Url = oneFMtlUrl;

      const mtlLoader = new THREE.MTLLoader();
      const mtlString = atob(base64Url);
      const metals = mtlLoader.parse(mtlString);

      const loader = new THREE.OBJLoader();
      loader.setMaterials(metals);
      const base64String = oneFapartmentUrl;

      const byteCharacters = atob(base64String);
      const byteArrays = [];
      for (let offset = 0; offset < byteCharacters.length; offset++) {
        const byteArray = new Uint8Array(1);
        byteArray[0] = byteCharacters.charCodeAt(offset);
        byteArrays.push(byteArray);
      }
      const byteArray = new Uint8Array(byteArrays.length);
      byteArrays.forEach((arr, i) => {
        byteArray[i] = arr[0];
      });

      const blob = new Blob([byteArray], { type: "application/octet-stream" });
      const url = URL.createObjectURL(blob);

      loader.load(
        url,
        (object) => {
          scene.add(object);
          model = object;
          const box = new THREE.Box3().setFromObject(model);
          console.log("模型的包围盒:", box);

          const boxHelper = new THREE.Box3Helper(box, 0xff0000);
          scene.add(boxHelper);

          setTimeout(() => {
            adjustCameraToFitModel();
          }, 100);
          URL.revokeObjectURL(url);
        },
        (xhr) => {
          console.log((xhr.loaded / xhr.total) * 100 + "% loaded");
        },
        (error) => {
          console.log("加载模型出错：", error);
        }
      );

      // 辅助线
      const axesHelper = new THREE.AxesHelper(5);
      scene.add(axesHelper);

      animate();
    }

    // 修改窗帘创建函数，确保初始位置正确
    function createCurtain() {
      // 清空旧的窗帘
      while (curtainAssembly.children.length > 0) {
        curtainAssembly.remove(curtainAssembly.children[0]);
      }

      const {
        windowWidth,
        windowHeight,
        windowY,
        segmentCount,
        rodRadius,
        ringRadius,
        ringThickness
      } = curtainParams;

      // 计算窗帘尺寸
      const totalCurtainWidth = windowWidth * 1.1;
      const curtainHeight = windowHeight * 1.2;
      const segmentWidth = totalCurtainWidth / segmentCount;

      // 窗帘杆 - 确保杆子长度与总宽度匹配
      const rodGeometry = new THREE.CylinderGeometry(rodRadius, rodRadius, totalCurtainWidth, 16);
      const rodMaterial = new THREE.MeshStandardMaterial({ color: 0x888888 });
      const rod = new THREE.Mesh(rodGeometry, rodMaterial);
      rod.rotation.z = Math.PI / 2;
      rod.position.y = windowY + windowHeight / 2 + 0.1;
      rod.position.x = 0; // 杆子居中
      curtainAssembly.add(rod);

      // 窗帘环和窗帘片
      const ringGeometry = new THREE.TorusGeometry(ringRadius, ringThickness, 16, 16);
      const ringMaterial = new THREE.MeshStandardMaterial({ color: 0xcccccc });

      // 窗帘材质
      const frontMaterial = new THREE.MeshStandardMaterial({
        color: 0xf5deb3,
        roughness: 0.9,
        metalness: 0.1,
        side: THREE.FrontSide,
        transparent: true,
        opacity: 0.9
      });

      const backMaterial = new THREE.MeshStandardMaterial({
        color: 0xf8ecc9,
        roughness: 0.9,
        metalness: 0.1,
        side: THREE.BackSide,
        transparent: true,
        opacity: 0.9
      });

      // 创建窗帘片和环
      for (let i = 0; i < segmentCount; i++) {
        // 创建带有褶皱的窗帘片几何体
        const segmentGeometry = createPleatedCurtainGeometry(segmentWidth, curtainHeight, 10, 20);

        const frontCurtain = new THREE.Mesh(segmentGeometry, frontMaterial);
        const backCurtain = new THREE.Mesh(segmentGeometry, backMaterial);

        // 初始位置 - 均匀分布在窗帘杆上
        const xPosition = -totalCurtainWidth / 2 + i * segmentWidth + segmentWidth / 2;
        const yPosition = rod.position.y - curtainHeight / 2;

        frontCurtain.position.set(xPosition, yPosition, 0);
        backCurtain.position.set(xPosition, yPosition, 0);

        frontCurtain.castShadow = true;
        backCurtain.castShadow = true;

        // 保存初始位置信息和索引
        frontCurtain.userData = {
          initialX: xPosition,
          index: i,
          segmentWidth: segmentWidth
        };
        backCurtain.userData = {
          initialX: xPosition,
          index: i,
          segmentWidth: segmentWidth
        };

        curtainAssembly.add(frontCurtain);
        curtainAssembly.add(backCurtain);

        // 窗帘环
        const ring = new THREE.Mesh(ringGeometry, ringMaterial);
        ring.position.set(xPosition, rod.position.y, 0);
        ring.userData = {
          initialX: xPosition,
          index: i
        };
        curtainAssembly.add(ring);
      }

      // 设置整个窗帘组的位置
      curtainAssembly.position.set(8, 0, 2.4);
    }

    // 新增辅助函数：获取边界位置
    function getCurtainBounds() {
      const totalCurtainWidth = curtainParams.windowWidth * 1.1;
      const segmentWidth = totalCurtainWidth / curtainParams.segmentCount;

      return {
        left: -totalCurtainWidth / 2 + segmentWidth / 2,
        right: totalCurtainWidth / 2 - segmentWidth / 2,
        segmentWidth: segmentWidth
      };
    }

    // 创建带有褶皱感的窗帘几何体
    function createPleatedCurtainGeometry(width, height, widthSegments, heightSegments) {
      const geometry = new THREE.PlaneGeometry(width, height, widthSegments, heightSegments);
      const positionAttribute = geometry.getAttribute('position');
      const positions = positionAttribute.array;

      // 添加褶皱效果
      for (let i = 0; i < positions.length; i += 3) {
        const x = positions[i];
        const y = positions[i + 1];

        // 基于位置添加褶皱波动
        const pleatIntensity = 0.03;
        const zOffset = Math.sin(x * Math.PI * 4) * Math.cos(y * Math.PI * 2) * pleatIntensity;

        positions[i + 2] = zOffset;
      }

      positionAttribute.needsUpdate = true;
      geometry.computeVertexNormals();

      return geometry;
    }

    // 更新窗帘状态 - 修复版：确保窗帘片真正移动
    function updateCurtain(position) {
      const openAmount = position / 100; // 0 到 1 之间
      console.log("收到位置:", position, "打开比例:", openAmount);

      if (!curtainAssembly) {
        console.warn("curtainAssembly 尚未初始化");
        return;
      }

      // 直接遍历 curtainAssembly 的子对象，不再依赖 geometry.type 过滤
      const curtainMeshes = [];
      curtainAssembly.traverse(child => {
        if (child.isMesh && child.userData && child.userData.initialX !== undefined) {
          curtainMeshes.push(child);
        }
      });

      console.log("需要移动的窗帘片/环数量:", curtainMeshes.length);

      const segmentCount = curtainParams.segmentCount;
      const totalCurtainWidth = curtainParams.windowWidth * 1.1;
      const segmentWidth = totalCurtainWidth / segmentCount;

      // 计算左右边界
      const leftBound  = -totalCurtainWidth / 2 + segmentWidth / 2;
      const rightBound =  totalCurtainWidth / 2 - segmentWidth / 2;

      curtainMeshes.forEach(mesh => {
        const index = mesh.userData.index;
        const initialX = mesh.userData.initialX;

        // 最左/最右两片作为“固定端”不移动
        if (index === 0 || index === segmentCount - 1) {
          mesh.position.x = initialX;
          return;
        }

        const isLeftSide = index < segmentCount / 2;

        // 目标：左侧片最终叠到最左，右侧片最终叠到最右
        let targetX;
        if (isLeftSide) {
          targetX = leftBound + (index * segmentWidth * 0.35); // 0.35 让叠放更紧凑
        } else {
          targetX = rightBound - ((segmentCount - 1 - index) * segmentWidth * 0.35);
        }

        // 线性插值
        const newX = initialX + openAmount * (targetX - initialX);
        mesh.position.x = newX;

        // 透明度随打开程度变化
        if (mesh.material.transparent !== undefined) {
          // mesh.material.opacity = Math.max(0.2, 0.9 - openAmount * 0.7);
        }

        // 褶皱效果
        if (mesh.geometry.type === 'PlaneGeometry') {
          adjustPleatingEffect(mesh.geometry, openAmount);
          const pileFactor = isLeftSide
            ? (mesh.position.x - leftBound) / (initialX - leftBound)
            : (rightBound - mesh.position.x) / (rightBound - initialX);
          adjustPileEffect(mesh.geometry, pileFactor);
        }
      });

      console.log("窗帘更新完成");
    }


    // 新增：根据堆积程度调整褶皱效果
    function adjustPileEffect(geometry, pileFactor) {
      const positionAttribute = geometry.getAttribute('position');
      const positions = positionAttribute.array;

      if (!geometry.userData.originalPositions) {
        geometry.userData.originalPositions = new Float32Array(positions);
      }

      const originalPositions = geometry.userData.originalPositions;

      // 堆积时增加褶皱效果
      for (let i = 0; i < positions.length; i += 3) {
        const x = originalPositions[i];
        const y = originalPositions[i + 1];

        // 基础褶皱
        const basePleat = Math.sin(x * Math.PI * 4) * Math.cos(y * Math.PI * 2) * 0.03;
        // 堆积褶皱（堆积越多褶皱越明显）
        const pilePleat = Math.sin(x * Math.PI * 8) * pileFactor * 0.05;

        positions[i + 2] = basePleat + pilePleat;
      }

      positionAttribute.needsUpdate = true;
      geometry.computeVertexNormals();
    }


    // 根据打开程度调整褶皱效果
    function adjustPleatingEffect(geometry, openAmount) {
      const positionAttribute = geometry.getAttribute('position');
      const positions = positionAttribute.array;

      // 如果没有保存原始位置，先保存
      if (!geometry.userData.originalPositions) {
        geometry.userData.originalPositions = new Float32Array(positions);
      }

      const originalPositions = geometry.userData.originalPositions;

      // 根据打开程度调整褶皱
      for (let i = 0; i < positions.length; i += 3) {
        const x = originalPositions[i];
        const y = originalPositions[i + 1];

        // 打开时褶皱减少
        const pleatIntensity = 0.03 * (1 - openAmount * 0.8);
        const zOffset = Math.sin(x * Math.PI * 4) * Math.cos(y * Math.PI * 2) * pleatIntensity;

        positions[i + 2] = zOffset;
      }

      positionAttribute.needsUpdate = true;
      geometry.computeVertexNormals();
    }

    // 调整整个窗帘组的位置（可根据需要调用）
    function setCurtainPosition(x, y, z) {
      curtainAssembly.position.set(x, y, z);
    }

    // 调整窗帘尺寸（可根据需要调用）
    function resizeCurtain(newWidth, newHeight) {
      curtainParams.windowWidth = newWidth;
      curtainParams.windowHeight = newHeight;
      createCurtain(); // 重新创建窗帘
    }

    // 俯视视角调整（原有代码保持不变）
    function adjustCameraToFitModel() {
      if (!model) return;

      const box = new THREE.Box3().setFromObject(model);
      const size = box.getSize(new THREE.Vector3());
      const center = box.getCenter(new THREE.Vector3());

      console.log("模型的大小:", size);
      console.log("模型的中心点:", center);

      const maxDimension = Math.max(size.x, size.y, size.z);
      const container = document.getElementById("scene-container");

      const scaleFactor = 15 / maxDimension;
      console.log("缩放比例:", scaleFactor);

      model.scale.set(scaleFactor, scaleFactor, scaleFactor);
      box.setFromObject(model);
      box.getSize(size);
      box.getCenter(center);

      console.log("缩放后模型大小:", size);
      console.log("缩放后中心点:", center);

      setupTopDownView(box, camera, container, center);
      controls.target.set(center.x, center.y, center.z);
      controls.update();
    }

    function setupTopDownView(box, camera, container, center) {
      const size = box.getSize(new THREE.Vector3());
      const maxDimension = Math.max(size.x, size.y, size.z);
      const aspect = container.clientWidth / container.clientHeight;

      let distance;
      if (aspect > 1) {
        distance = size.y / (2 * Math.tan(camera.fov * Math.PI / 360));
      } else {
        distance = size.x / (2 * Math.tan(camera.fov * Math.PI / 360) * aspect);
      }

      distance *= 8;
      console.log("计算的距离:", distance);

      camera.position.set(center.x, center.y + distance, center.z);
      camera.lookAt(center.x, center.y, center.z);

      // 双击事件返回俯视视角
      renderer.domElement.addEventListener("dblclick", () => {
        camera.position.set(center.x, center.y + distance, center.z);
        camera.lookAt(center.x, center.y, center.z);
        controls.update();
      });

      camera.near = Math.max(0.1, distance * 0.1);
      camera.far = distance * 5;
      camera.updateProjectionMatrix();

      console.log("相机位置:", camera.position);
      console.log("相机看向点:", center);
    }

    function onWindowResize() {
      const container = document.getElementById("scene-container");
      camera.aspect = container.clientWidth / container.clientHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(container.clientWidth, container.clientHeight);
    }

    function animate() {
      requestAnimationFrame(animate);
      controls.update();
      renderer.render(scene, camera);
    }

    // WebSocket相关逻辑（保持不变）
    function connectWebSocket() {
      socket = new WebSocket(haUrl);
      socket.onopen = (e) => {
        console.log("WebSocket连接已打开:", e);
        authenticate();
      };
      socket.onmessage = (e) => {
        console.log("WebSocket收到消息:", e);
        handleWebSocketMessage(JSON.parse(e.data));
      };
      socket.onclose = () => setTimeout(connectWebSocket, 5000);
      socket.onerror = () => { };
    }

    function handleWebSocketMessage(message) {
      console.log("处理WebSocket消息:", message);

      switch (message.type) {
        case "auth_required":
          authenticate();
          break;
        case "auth_ok":
          subscribeToEvents();
          break;
        case "result":
          if (message.success && message.id === 2) {
            subscriptionId = message.result.id;
            getCurrentState();
          }
          break;
        case "event":
          if (message.event && message.event.event_type === "state_changed") {
            const newState = message.event.data.new_state;
            if (newState && newState.entity_id === entityId) updateUI(newState);
          }
          break;
      }
    }

    function authenticate() {
      console.log("认证函数调用");
      socket.send(JSON.stringify({ type: "auth", access_token: accessToken }));
    }

    function subscribeToEvents() {
      console.log("订阅事件函数调用");
      socket.send(JSON.stringify({
        id: messageId++,
        type: "subscribe_events",
        event_type: "state_changed",
      }));
    }

    function getCurrentState() {
      console.log("调用获取状态的函数");
      const id = messageId++;
      socket.send(JSON.stringify({ id, type: "get_states" }));

      const originalOnMessage = socket.onmessage;
      socket.onmessage = (e) => {
        const msg = JSON.parse(e.data);
        if (msg.type === "result" && msg.id === id) {
          if (msg.success) {
            const curtain = msg.result.find((s) => s.entity_id === entityId);
            if (curtain) updateUI(curtain);
          }
          socket.onmessage = originalOnMessage;
        } else originalOnMessage(e);
      };
    }

    function updateUI(state) {
      const position = state.attributes.current_position ?? (state.state === "open" ? 100 : 0);
      updateCurtain(position);
    }

    window.onload = () => {
      initThreeJS();
      connectWebSocket();
    };
  </script>
</body>

</html>